package com.songyang.yougnsql.core.data.pagecache;

import com.songyang.yougnsql.core.cache.AbstractCache;
import com.songyang.yougnsql.common.ErrorMsg;
import com.songyang.yougnsql.common.ExceptionHandler;
import com.songyang.yougnsql.common.YoungSQLException;
import com.songyang.yougnsql.core.data.page.Page;
import com.songyang.yougnsql.core.data.page.PageImpl;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName PageCacheImpl
 * @Description 所有的页面都要从该类获取，如果缓存有则从页面缓存获取，没有则回源
 * @date 2022/12/5 15:00
 * @Author yanceysong
 * @Version 1.0
 */
public class PageCacheImpl extends AbstractCache<Page> implements PageCache, AutoCloseable {
    public static final String DB_SUFFIX = ".db";
    /**
     * 最小的缓存页面数量
     */
    private static final int MEM_MIN_LIM = 10;
    private final RandomAccessFile randomAccessFile;
    private final FileChannel fileChannel;
    private final Lock fileLock;
    /**
     * 当前的最大pageNumber
     */
    private final AtomicInteger pageNumbers;

    PageCacheImpl(RandomAccessFile file, FileChannel fileChannel, int maxResource) {
        super(maxResource);
        if (maxResource < MEM_MIN_LIM) {
            try {
                throw new YoungSQLException(ErrorMsg.MEM_TOO_SMALL_EXCEPTION);
            } catch (YoungSQLException e) {
                ExceptionHandler.handler(e);
            }
        }
        long length = 0;
        try {
            length = file.length();
        } catch (IOException e) {
            ExceptionHandler.handler(e);
        }
        this.randomAccessFile = file;
        this.fileChannel = fileChannel;
        this.fileLock = new ReentrantLock();
        this.pageNumbers = new AtomicInteger((int) length / Page.PAGE_SIZE);
    }

    /**
     * 根据页面号获取页面在数据库文件当中的偏移量
     *
     * @param pageNumber 页面号
     * @return 偏移量
     */
    private long getPageOffset(int pageNumber) {
        return (long) (pageNumber - 1) * Page.PAGE_SIZE;
    }

    /**
     * 创建一个页面
     *
     * @param initData 页面数据
     * @return 新的页面的页面号
     */
    @Override
    public int newPage(byte[] initData) {
        int newPageNumber = pageNumbers.incrementAndGet();
        flushPage(new PageImpl(newPageNumber, initData, null));
        return newPageNumber;
    }

    /**
     * 通过pageNumber获取一个页面,如果缓存有就从缓存拿，没有就回源
     *
     * @param pageNumber 页面号
     * @return 页面
     */
    @Override
    public Page getPage(int pageNumber) {
        try {
            return get(pageNumber);
        }catch (Exception e){
            return null;
        }

    }

    @Override
    public void close() {
        try {
            super.close();
            randomAccessFile.close();
            fileChannel.close();
        } catch (IOException e) {
            ExceptionHandler.handler(e);
        }
    }

    /**
     * 根据pageNumber从数据库文件中读取页数据，并包裹成Page
     *
     * @param key 缓存的key，这里指pageNumber
     * @return 从数据库文件读出来的page
     */
    @Override
    protected Page getForCache(long key) {
        int pageNumber = (int) key;
        long pageOffset = getPageOffset(pageNumber);
        ByteBuffer raw = ByteBuffer.allocate(Page.PAGE_SIZE);
        fileLock.lock();
        try {
            fileChannel.position(pageOffset);
            fileChannel.read(raw);
            //只有从pageCache中获取过一次后，才会有对应的pageCache
            return new PageImpl(pageNumber, raw.array(), this);
        } catch (IOException e) {
            ExceptionHandler.handler(e);
        } finally {
            fileLock.unlock();
        }
        return null;
    }

    /**
     * 在缓存当中释放一个page
     *
     * @param page 页面
     */
    @Override
    protected void releaseForCache(Page page) {
        if (page.isDirty()) {
            flushPage(page);
            page.setDirty(false);
        }
    }

    /**
     * 释放一个缓存页面
     *
     * @param page 页面
     */
    @Override
    public void release(Page page) {
        release(page.getPageNumber());
    }

    /**
     * 截断错误页面
     *
     * @param maxPageNumber 当前最大的pageNumber
     */
    @Override
    public void truncateByPageNumber(int maxPageNumber) {
        long pageOffset = getPageOffset(maxPageNumber);
        try {
            randomAccessFile.setLength(pageOffset);
        } catch (IOException e) {
            ExceptionHandler.handler(e);
        }
        pageNumbers.set(maxPageNumber);
    }

    /***
     * 获取页号其实就是新建一个页面时候应该分配第几页
     * @return 第几页
     */
    @Override
    public int getPageNumber() {
        return this.pageNumbers.intValue();
    }

    /**
     * 将一个页面数据强制写入到数据库文件
     *
     * @param page 页面
     */
    @Override
    public void flushPage(Page page) {
        int pageNumber = page.getPageNumber();
        //获取这个页面应该所在的偏移量
        long pageOffset = getPageOffset(pageNumber);
        fileLock.lock();
        try {
            ByteBuffer buf = ByteBuffer.wrap(page.getData());
            fileChannel.position(pageOffset);
            //找到对应位置直接写入
            fileChannel.write(buf);
            fileChannel.force(false);
        } catch (IOException e) {
            ExceptionHandler.handler(e);
        } finally {
            fileLock.unlock();
        }
    }
}
